iT邦幫忙

2024 iThome 鐵人賽

DAY 4
0

First Commit

就讓我們進入寫扣環節。

為了先能有一個可以跑的程式,我做出以下限制:

  • 無互動 (State 不需要實作,不會需要 rerun 按鈕)
  • 提供 Component:
    • Text
    • Button (按下去沒功能)
  • 溝通時先不用 Websocket,每次更新都是直接傳一個代表畫面的 JSON。之後再來看看該怎麼使用 Websocket。
  • JS Framework: Vanilla.js :laughing: ,我們先用最原始的方式,之後再來看看要用什麼 framework。
  • Golang: http server 直接用官方原生的 http 包。

API

Server 會提供一個 endpoint,然後會回一個結構如下的 JSON:

{
  "comps": [{
    "type": "text || button,之後會隨著 component 增加而增加",
    "id": "這個 component 的唯一 id",
    
    # text type 會有的屬性
    "text": "type 為 text 時顯示的文字",
    
    # button type 會有的屬性
    "label": "type 為 button 時在 button 上的文字",
  }, {
    # ...更多 text/button component 
  }]
}

Components

Container

目前來說 Container 的功能就只是提供第一層的 Component List,他本身甚至不會當 Component。

type Container struct {
	Type string `json:"type"`
	ID   string `json:"id"`
    
	// 目前只有這有作用
	Comp []any  `json:"comp"`
}

func NewContainer(id string) *Container {
	return &Container{
		Type: "container",
		ID:   id,
	}
}

Text

Text Component 就只是一行文字,我們也先不給其他屬性,只需要 Text。

type textComp struct {
	Type string `json:"type"`
	ID   string `json:"id"`
	Text string `json:"text"`
}

func newText(text string) *textComp {
	return &textComp{
		Type: "text",
		ID:   text,
		Text: text,
	}
}

func Text(c *Container, text string) {
	c.Comp = append(c.Comp, newText(text))
}

Button

Button Component 目前並沒有互動功能,只是在按鈕上顯示 label,所以 Button 函數直接先 return false。我們晚點把 State 加進來。

type buttonComp struct {
	Type  string `json:"type"`
	ID    string `json:"id"`
	Label string `json:"label"`
}

func newButton(label string) *buttonComp  {
	return &buttonComp{
		Type:  "button",
		ID:    label,
		Label: label,
	}
}

func Button(c *Container, label string) bool {
	c.Comp = append(c.Comp, newButton(label))
    
	// 我們暫時還沒有 State 可以拿,只能先這樣
	return false
}

Service

Server 的邏輯。

index.html

<script>
    function createButton(comp) {
        const newButton = document.createElement('button')
        newButton.innerText = comp.label
        return newButton
    }
    
    function createText(comp) {
        const newText = document.createElement('div')
        newText.innerText = comp.text
        return newText
    }
    
    // 只有先在一開始觸發。
    fetch('/api/run').then(resp => resp.json()).then(body => {
        var rootDiv = document.getElementById("root")
        body.comp.forEach(comp => {
            if (comp.type === 'button') {
                rootDiv.appendChild(createButton(comp))
            } else if (comp.type === 'text') {
                rootDiv.appendChild(createText(comp))
            } else {
                console.warn(e.type)
            }
        });
    })
</script>
<div id="root">
</div>

pkg.go

在 API 部分,我們就是跑完 Script 後,把 Container JSON Marshal 起來傳回去。

//go:embed index.html
var indexBody []byte
func Run(f func(*Container)) {
	// 取得畫面JSON
	http.HandleFunc("/api/run", func(w http.ResponseWriter, r *http.Request) {
		rootContainer := NewContainer("root")
		f(rootContainer)
		bs, err := json.Marshal(rootContainer)
		if err != nil {
			w.WriteHeader(http.StatusInternalServerError)
			return
		}
		w.Write(bs)
	})

	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		w.Write(indexBody)
	})

	http.ListenAndServe(":8763", nil)
}

User

終於,我們踏出了第一步,只是目前連和使用者互動都做不到。

main.go

package main
func main() {
	Run(func(root *Container) {
		Button(root, "hello")
		Text(root, "world")
	})
}

https://ithelp.ithome.com.tw/upload/images/20240827/20151240owC5EHAOXA.png


上一篇
Day3 介面的初步想法
下一篇
Day5 讓 Container 也是 Component
系列文
用 Golang 實作 streamlit 30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言